package com.example.verticle;

import com.github.rjeschke.txtmark.Processor;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.SQLConnection;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.templ.freemarker.FreeMarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

public class MainVerticle extends AbstractVerticle {

    // tag::sql-fields[]
    private static final String SQL_CREATE_PAGES_TABLE = "create table if not exists Pages (Id integer identity primary key, Name varchar(255) unique, Content clob)";
    private static final String SQL_GET_PAGE = "select Id, Content from Pages where Name = ?"; // <1>
    private static final String SQL_CREATE_PAGE = "insert into Pages values (NULL, ?, ?)";
    private static final String SQL_SAVE_PAGE = "update Pages set Content = ? where Id = ?";
    private static final String SQL_ALL_PAGES = "select Name from Pages";
    private static final String SQL_DELETE_PAGE = "delete from Pages where Id = ?";
    // end::sql-fields[]

    // tag::db-and-logger[]
    private JDBCClient dbClient;

    private static final Logger LOGGER = LoggerFactory.getLogger(MainVerticle.class);
    // end::db-and-logger[]

    @Override
    public void start(Promise<Void> startPromise) throws Exception {
        Future<Void> steps = prepareDatabase().compose(v->startHttpServer());
        steps.onComplete(startPromise);
    }

    // tag::prepareDatabase[]
    private Future<Void> prepareDatabase(){
        Promise<Void> promise = Promise.promise();
        dbClient=JDBCClient.createShared(vertx, new JsonObject() // <1>
                .put("url", "jdbc:hsqldb:file:/home/liulei/develop/hsqldb/wiki")   // <2>
                .put("driver_class", "org.hsqldb.jdbcDriver")   // <3>
                .put("max_pool_size", 30));   // <4>
        dbClient.getConnection(ar->{
           if (ar.failed()){
               LOGGER.error("Could not open a database connection", ar.cause());
               promise.fail(ar.cause());
           }else {
               SQLConnection connection = ar.result();
               connection.execute(SQL_CREATE_PAGES_TABLE,create->{
                   connection.close();
                   if (create.failed()){
                       LOGGER.error("Database preparation error",create.cause());
                       promise.fail(create.cause());
                   }else {
                        LOGGER.info("init db success");
                        promise.complete();
                   }
               });
           }
        });
        return promise.future();
    }
    // end::prepareDatabase[]

    // tag::startHttpServer[]
    private FreeMarkerTemplateEngine templateEngine;

    private Future<Void> startHttpServer() {
        Promise<Void> promise = Promise.promise();
        HttpServer server = vertx.createHttpServer();
        templateEngine= FreeMarkerTemplateEngine.create(vertx);

        Router router = Router.router(vertx);
        router.post().handler(BodyHandler.create());//允许接收body数据

        router.get("/").handler(this::indexHandler);
        router.get("/wiki/:page").handler(this::pageRenderingHandler);
        router.post("/save").handler(this::pageUpdateHandler);
        router.post("/create").handler(this::pageCreateHandler);
        router.post("/delete").handler(this::pageDeletionHandler);

        server.requestHandler(router)
                .listen(8080,ar->{
                    if (ar.succeeded()){
                        LOGGER.info("HTTP server running on port 8080");
                        promise.complete();
                    }else {
                        LOGGER.error("Could not start a HTTP server", ar.cause());
                        promise.fail(ar.cause());
                    }
                });

        return promise.future();
    }
    // end::startHttpServer[]

    // tag::indexHandler[]
    private void indexHandler(RoutingContext context) {
        dbClient.getConnection(connar->{
            if(connar.succeeded()){
                SQLConnection conn = connar.result();
                conn.query(SQL_ALL_PAGES,res->{
                    conn.close();
                    if (res.succeeded()){
                        List<String> pages = res.result()
                                .getResults()
                                .stream()
                                .map(json -> json.getString(0))
                                .sorted()
                                .collect(Collectors.toList());

                        context.put("title","Wiki home");
                        context.put("pages",pages);
                        templateEngine.render(context.data(),"templates/index.ftl",ar->{
                            if (ar.succeeded()){
                                context.response().putHeader("Content-Type", "text/html");
                                context.response().end(ar.result());
                            }else {
                                context.fail(ar.cause());
                            }
                        });
                    }else {
                        context.fail(res.cause());
                    }
                });
            }else {
                context.fail(connar.cause());
            }
        });
    }
    // end::indexHandler[]

    // tag::pageRenderingHandler[]
    private static final String EMPTY_PAGE_MARKDOWN =
            "# A new page\n" +
                    "\n" +
                    "Feel-free to write in Markdown!\n";
    private void pageRenderingHandler(RoutingContext context){
        String page = context.request().getParam("page");

        dbClient.getConnection(
                scar -> {
                    if (scar.succeeded()){
                        SQLConnection connection = scar.result();
                        connection.queryWithParams(SQL_GET_PAGE,new JsonArray().add(page),res->{
                            connection.close();
                            if (res.succeeded()){
                                List<JsonArray> results = res.result().getResults();
                                JsonArray row = results
                                        .stream()
                                        .findFirst()
                                        .orElseGet(() -> new JsonArray().add(-1).add(EMPTY_PAGE_MARKDOWN));
                                Integer id = row.getInteger(0);
                                String content = row.getString(1);

                                context.put("title",page);
                                context.put("id",id);
                                context.put("newPage", results.size()==0?"yes":"no");
                                context.put("rawContent",content);
                                context.put("content", Processor.process(content));
                                context.put("timestamp", LocalDateTime.now().toString());

                                templateEngine.render(context.data(),"templates/page.ftl",ar->{
                                    if (ar.succeeded()){
                                        context.response().putHeader("Content-Type", "text/html");
                                        context.response().end(ar.result());
                                    }else {
                                        context.fail(ar.cause());
                                    }
                                });
                            }else {
                                context.fail(res.cause());
                            }
                        });
                    }else {
                        context.fail(scar.cause());
                    }
                }
        );
    }
    // end::pageRenderingHandler[]

    // tag::pageUpdateHandler[]
    private void pageUpdateHandler(RoutingContext context) {
        //1.获取参数
        String id = context.request().getParam("id");
        String title = context.request().getParam("title");
        String markdown = context.request().getParam("markdown");
        boolean newPage = "yes".equals(context.request().getParam("newPage"));

        //2.连接db保存数据
        dbClient.getConnection(scar->{
            if (scar.succeeded()){
                SQLConnection connection = scar.result();
                String sql=newPage?SQL_CREATE_PAGE:SQL_SAVE_PAGE;
                JsonArray params = new JsonArray();

                if (newPage){
                    params.add(title).add(markdown);
                }else {
                    params.add(markdown).add(id);
                }
                connection.updateWithParams(sql,params,res->{
                    connection.close();
                    if (res.succeeded()){
                        context.response().setStatusCode(303);
                        context.response().putHeader("Location", "/wiki/" + title);
                        context.response().end();
                    }else {
                        context.fail(res.cause());
                    }
                });
            }else {
                context.fail(scar.cause());
            }
        });
    }
    // end::pageUpdateHandler[]

    // tag::pageCreateHandler[]
    private void pageCreateHandler(RoutingContext context) {
        //1.获取请求参数
        String name = context.request().getParam("name");
        String location="/wiki/"+name;
        if (StringUtils.isBlank(name)){
            location="/";
        }

        context.response().setStatusCode(303);
        context.response().putHeader("location",location);
        context.response().end();
    }
    // end::pageCreateHandler[]

    // tag::pageDeletionHandler[]
    private void pageDeletionHandler(RoutingContext context) {
        //1.获取参数
        String id = context.request().getParam("id");
        //2.数据库操作 --删除
        dbClient.getConnection(scar->{
            if (scar.succeeded()){
                SQLConnection connection = scar.result();
                connection.updateWithParams(SQL_DELETE_PAGE,new JsonArray().add(id), res->{
                    connection.close();
                    if (res.succeeded()){
                        context.response().setStatusCode(303);
                        context.response().putHeader("location","/");
                        context.response().end();
                    }else {
                        context.fail(res.cause());
                    }
                });
            }else {
                context.fail(scar.cause());
            }
        });
    }
    // end::pageDeletionHandler[]
}
